/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

#include "splashcore.h"

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Construction / destruction

CppObjectNode::CppObjectNode(
	BuildGraph* graph,
	CppSourceNode* srcfile,
	CppToolchain* toolchain,
	const CppCompileFlagList& cflags)
	: BuildGraphNode(graph)
	, m_srcfile(srcfile)
	, m_toolchain(toolchain)
	, m_cflags(cflags)
{
	//Get file name
	string srcname = m_srcfile->GetOutputPath();
	size_t pos = srcname.rfind("/");
	string fname = srcname.substr(pos+1);
	
	//Trim off extension
	pos = fname.rfind(".");
	string base = fname;
	if(pos != string::npos)
		base = fname.substr(0, pos);
	
	//Object file name
	m_name = base + ".o";
	
	//Add Splash include directory and metadata directory
	//TODO: don't hard-code Splash path
	m_cflags.push_back(new CppIncludeDirectoryFlag("/nfs/home/azonenberg/Documents/local/programming/splash-build-system/trunk/src"));
	m_cflags.push_back(new CppIncludeDirectoryFlag(m_graph->GetOutDir() + "/" + m_toolchain->GetArchitecture() + "/include"));
	m_cflags.push_back(new CppIncludeDirectoryFlag(m_graph->GetOutDir() + "/generic/include"));
	
}

CppObjectNode::~CppObjectNode()
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Compilation

string CppObjectNode::GetOutputPath()
{
	return GetBinaryPath();
}

string CppObjectNode::GetDescription()
{
	char desc[1024];
	snprintf(desc, sizeof(desc), "C++ object %s [%s]",
		str_replace(m_graph->GetSourceDir(), "", m_srcfile->GetOutputPath()).c_str(),
		m_toolchain->GetArchitecture().c_str());
	return string(desc);
}

string CppObjectNode::GetBinaryPath()
{
	return GetBinaryPathForHash(GetHashCached());
}

string CppObjectNode::GetBinaryPathForHash(std::string hash)
{
	return m_graph->GetMetaDir() + "/" + hash + "/" + m_name;
}

string CppObjectNode::RemoveImplicitFilenames(string line)
{
	//Remove our filename
	return str_replace(m_srcfile->GetOutputPath() + ":", "", line);
}

void CppObjectNode::Update(Cluster* cluster)
{
	string outdir   = m_graph->GetMetaDir() + "/" + GetHashCached();
	string binpath  = outdir  + "/" + m_name;
			
	//Rebuild our dependencies (in case any source files were auto-generated, etc)
	//Need to flatten dependencies since it's possible for a non-generated source file to depend on a generated header
	unordered_set<BuildGraphNode*> fdeps;
	ComputeFlattenedDependencies(fdeps);
	vector<ClusterJob*> deps;
	for(auto it = fdeps.begin(); it != fdeps.end(); it++)
	{
		BuildGraphNode* node = *it;
		DebugPrintfSilent(3, "    Dependency: %s\n", node->GetDescription().c_str());
		node->UpdateCached(cluster);
		if(node->GetBuildJob() != NULL)
			deps.push_back(node->GetBuildJob());
	}
	
	//Invoke the C++ compiler to do all of the dirty work
	m_job = m_toolchain->CompileSourceToObject(
		m_srcfile->GetOutputPath(),
		binpath,
		m_cflags,
		deps,
		cluster,
		m_graph->GetSourceDir()
		);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dependency scanning

std::string CppObjectNode::GetHash()
{
	DebugPrintfHeading(5, "CppObjectNode::GetHash()\n");
	DebugPrintfSilent(6, "    source file %s\n", m_srcfile->GetOutputPath().c_str());
	string hashin = "";
	
	//Update our dependencies
	UpdateDependencies();
	
	//Add data at this node
	hashin += sha256(m_name);
	hashin += m_toolchain->GetHash();
	hashin += m_toolchain->GetCflagsHash(m_cflags);
	
	//Add data for our dependencies
	for(size_t i=0; i<m_dependencies.size(); i++)
		hashin += m_dependencies[i]->GetHashCached(); 
	
	//Compute the final hash
	string hash = sha256(hashin);
	
	//Add us to the list of directories to search when profiling
	m_graph->AddProfileDir(m_toolchain->GetArchitecture(), GetDirOfFile(GetBinaryPathForHash(hash)));
	
	return hash;
}

void CppObjectNode::UpdateDependencies()
{
	DebugPrintfHeading(5, "CppObjectNode::UpdateDependencies()\n");
	DebugPrintfSilent(5, "    source file %s\n", m_srcfile->GetOutputPath().c_str());

	//Initially, we depend on our source file only
	m_dependencies.clear();
	m_dependencies.push_back(m_srcfile);
	
	//Use our internal dependency scanner to scan the source files for header references
	std::list<BuildGraphNode*> rstack;
	FastDependencyScanner(m_srcfile->GetOutputPath(), this, rstack);
}

/**
	@brief Fast dependency scanner for dealing with lots of headers.
	
	Massively faster than gcc because it does not re-parse every header file for every source file, instead re-using 
	the existing graph nodes. This results in O(M+N) run time for M headers included by N sources, rather than O(MN) 
	as one would get from running gcc -E.
 */
void CppObjectNode::FastDependencyScanner(std::string source_file, BuildGraphNode* node, std::list<BuildGraphNode*>& rstack)
{
	DebugPrintfHeading(5, "CppObjectNode::FastDependencyScanner()\n");
	DebugPrintfSilent(5, "    source file %s\n", source_file.c_str());
	
	//double start = GetTime();
	
	//Check if it's auto-generated, if so skip it
	//(the generation code is responsible for updating dependencies)
	BuildGraphNode* genfile = m_graph->GetNodeForPath(source_file, m_toolchain->GetArchitecture());
	if(dynamic_cast<CppGeneratedSourceNode*>(genfile) != NULL)
	{
		DebugPrintfSilent(6, "    FastDependencyScanner: Skipping %s because it is automatically generated\n", source_file.c_str());
		return;
	}
	
	//Push us onto the stack so we can avoid cycles
	rstack.push_back(node);
	
	FILE* fp = fopen(source_file.c_str(), "r");
	if(!fp)
		FatalError("CppObjectNode::FastDependencyScanner(): cannot open source file %s\n", source_file.c_str());
	
	//List of system directories to look for includes.
	//If doing angle brackets, search here only.
	//If not doing angle brackets, search the current directory after here.
	vector<string> include_paths;
	include_paths.push_back(m_graph->GetSourceDir() + "/include");
	//TODO: Check compiler flags and do stuff per target
	include_paths.push_back("/usr/include");
	include_paths.push_back("/usr/local/include");
	//TODO: Look at CPLUS_INCLUDE_PATH
	
	//Add compiler-specific include paths
	m_toolchain->GetCompilerIncludePaths(include_paths);
	
	//Add include paths in cflags
	for(size_t i=0; i<m_cflags.size(); i++)
	{
		CppIncludeDirectoryFlag* flag = dynamic_cast<CppIncludeDirectoryFlag*>(m_cflags[i]);
		if(flag == NULL)
			continue;
		include_paths.push_back(flag->GetPath());
	}
	
	//Read each line and see if it's a preprocessor directive. If so, process it.
	char line[2048];
	int nline = 0;
	bool folding = false;
	string fold_previous;
	bool in_comment = false;
	while(fgets(line, sizeof(line), fp) != NULL)
	{
		char* pline = line;
		while(isspace(*pline))
			pline++;
		
		//Trim off whitespace at end of line and add folded lines together
		string sline = pline;
		if(folding)
		{
			sline = fold_previous + line;
			folding = false;
		}
		sline.erase(sline.find_last_not_of(" \n\r\t")+1);

		//Bump line count
		nline++;
		
		//If there's a \ at the end, fold lines
		if(sline[sline.length() - 1] == '\\')
		{
			//Delete the \ first
			sline.erase(sline.find_last_not_of("\\")+1);
			
			//Save and continue
			fold_previous = sline;
			folding = true;
			continue;
		}
		
		//Look for /*'s and "s
		bool in_quote = false;
		for(size_t i=0; i<sline.length(); i++)
		{
			//In comment - look for end of comment blocks
			if(in_comment)
			{
				if(sline[i] == '/')
				{
					if( ((i+1) < sline.length()) && (sline[i+1] == '*') )
					{
						DebugPrintfSilent(2, "WARNING: Nested comments not allowed in C/C++ in %s (on line %d)\n",
							source_file.c_str(), nline);
					}
					
					if( (i > 0) && (sline[i-1] == '*'))
						in_comment = false;
				}
			}
			
			//Not in comment - look for quotes and starts of comment blocks
			else
			{
				if(sline[i] == '\"')
				{
					//\" is ignored
					if( (i > 0) && (sline[i-1] == '\\'))
						continue;
					
					//Start or end of quoted text
					in_quote = !in_quote;						
				}
				
				else if( (sline[i] == '/') && !in_quote )
				{
					if((i+1) < sline.length())
					{
						//C-style comment
						if(sline[i+1] == '*')
							in_comment = true;
					
						//C++ style comments
						else if(sline[i+1] == '/')
						{
							//Trim off everything from here to the end of the line
							sline = sline.substr(0, i);
							break;
						}
					}
				}
			}
		}
		
		//In comment? Skip all subsequent processing
		if(in_comment)
			continue;
		
		//Skip anything not preprocessor
		if(sline[0] != '#')
			continue;
			
		//Get the command
		char cmd[2048];
		sscanf(sline.c_str(), "# %2047[^<\" \t\n(/]", cmd);
		string scmd = cmd;
			
		//Ignore preprocessor conditionals, find all includes
		if( (scmd == "if") || (scmd == "ifdef") || (scmd == "else") || (scmd == "ifndef") || (scmd == "elif") ||
			(scmd == "endif") || (scmd == "define") || (scmd == "undef"))
		{	
			//no action
		}
		
		//Ignore warning/pragma/error
		else if( (scmd == "warning") || (scmd == "pragma") || (scmd == "error"))
		{
			//no action
		}
		
		else if(scmd == "include_next")
		{				
			//Get the path of the current source file
			string base = GetDirOfFile(source_file);
			DebugPrintfSilent(5, "    include_next: base dir is %s\n", base.c_str());
			DebugPrintfSilent(5, "    file %s, line %d: %s\n", source_file.c_str(), nline, sline.c_str());
			
			//Find the base name in the include path list
			size_t j;
			for(j=0; j<include_paths.size(); j++)
			{
				if(include_paths[j] == base)
					break;
			}
			
			//Process it
			ProcessIncludeDirective(sline, include_paths, nline, source_file, node, rstack, j+1);
		}
	
		//Process includes
		else if(scmd == "include")
			ProcessIncludeDirective(sline, include_paths, nline, source_file, node, rstack);
		
		else
			FatalError("Unknown preprocessor command: %s (on line %d)\n", sline.c_str(), nline);
	}
	
	fclose(fp);
	
	rstack.pop_back();
	
	//double dt = GetTime() - start;
	//DebugPrintfSilent(1, "    FastDependencyScanner() for %s completed in %.2f ms\n", source_file.c_str(), dt * 1000);
}

void CppObjectNode::ProcessIncludeDirective(
	string sline,
	const vector<string>& include_paths,
	int nline,
	string source_file,
	BuildGraphNode* node,
	std::list<BuildGraphNode*>& rstack,
	size_t include_path_start
	)
{
	string arch = m_toolchain->GetArchitecture();
	
	bool anglebracket = false;
	if(strstr(sline.c_str(), "<") != NULL)
		anglebracket = true;
		
	//Parse out the file name (match both include and include_next)
	char fname[2048];
	if(anglebracket)
		sscanf(sline.c_str(), "# inc%*[^<] <%2047[^>]>", fname);
	else
		sscanf(sline.c_str(), "# inc%*[^\"] \"%2047[^\"]\"", fname);
		
	//TODO: if file name is a macro, expand it
	
	if(!anglebracket)
		DebugPrintfSilent(5, "    Found include directive: \"%s\"\n", fname);
	else
		DebugPrintfSilent(5, "    Found include directive: <%s>\n", fname);
	
	//Resolve the file name
	string abs_path;
	if(!anglebracket)
	{
		//Try searching in the current directory
		string base = GetDirOfFile(source_file);
		string guesspath = base + "/" + fname;
		DebugPrintfSilent(6, "    Guessing path %s\n", guesspath.c_str());
		if(DoesFileExist(guesspath))
			abs_path = CanonicalizePath(guesspath);
	}
	if(abs_path.empty())
	{
		for(size_t i=include_path_start; i<include_paths.size(); i++)
		{
			string guesspath = include_paths[i] + "/" + fname;
			DebugPrintfSilent(6, "    Guessing path %s\n", guesspath.c_str());
			
			//Do a quick sanity check
			//Files outside the source directory should not include config files etc in the build directory
			if(
				(source_file.find(m_graph->GetSourceDir()) == source_file.npos) &&
				(guesspath.find(m_graph->GetOutDir()) != guesspath.npos)
			)
			{
				//printf("Skipping source %s\n    guess %s\n", source_file.c_str(), guesspath.c_str());
				//printf("    outdir = %s\n", m_graph->GetOutDir().c_str());
				continue;
			}
			
			//Check if it's a generated file
			BuildGraphNode* genfile = m_graph->GetNodeForPath(guesspath, arch);
			if(dynamic_cast<CppGeneratedSourceNode*>(genfile) != NULL)
			{
				DebugPrintfSilent(6, "    File %s exists but is auto-generated...\n", guesspath.c_str());
				m_dependencies.push_back(genfile);
				abs_path = guesspath;
				break;
			}
			
			//No, normal search
			else if(DoesFileExist(guesspath))
			{				
				DebugPrintfSilent(6, "    File exists\n");
				abs_path = guesspath;
				break;
			}
		}
	}
	
	//See if we found it
	if(abs_path.empty())
	{
		DebugPrintfSilent(3,
			"Could not resolve include file reference \"%s\"\n"
			"    on line %d\n"
			"    of file %s\n", 
			sline.c_str(), nline, source_file.c_str());
		return;
	}
	DebugPrintfSilent(5, "    Resolved include file path: %s\n", abs_path.c_str());

	//See if this file exists in the graph
	BuildGraphNode* hnode = m_graph->GetNodeForPath(abs_path, arch);
	if(hnode == NULL)
	{
		//Create new node
		DebugPrintfSilent(6, "    header file %s is not in build graph, creating new node\n", abs_path.c_str());
		hnode = new CppSourceNode(m_graph, abs_path, arch);
		m_graph->AddNodeWithPath(hnode, abs_path, arch);
		
		//Recursively evaluate dependencies of the source file
		FastDependencyScanner(abs_path, hnode, rstack);
	}
	else
		DebugPrintfSilent(6, "    header file %s is already in build graph, adding an edge\n", abs_path.c_str());
	
	//Check for cycles
	bool cycle_found = false;
	for(std::list<BuildGraphNode*>::iterator it=rstack.begin(); it!=rstack.end(); it++)
	{
		if(*it == hnode)
		{
			DebugPrintf(6, "Cycle found (%s)\n", abs_path.c_str());
			cycle_found = true;
		}
	}
		
	//No cycle? Add the edge, all is well
	if(!cycle_found)
		node->m_dependencies.push_back(hnode);
}

bool CppObjectNode::GetFilteredOutput(std::vector<std::string>& output)
{
	static const char* blacklist[]=
	{
		"Same expression on both sides of"		//False positive if using macro flags that evaluate to 0 etc
	};
	
	return BuildGraphNode::GetFilteredOutput(
		output,
		NULL,
		0,
		blacklist,
		sizeof(blacklist)/sizeof(blacklist[0])
		);
}
